import type {AxiosRequestConfig, AxiosResponse, Method as HttpMethod,AxiosStatic} from 'axios';

export interface BaseParams {
    params?: Record<string, unknown>;
}

// const

export type PathItemObject = { [M in HttpMethod]: OperationObject } & { parameters?: any };

export interface OperationObject {
    parameters: any;
    requestBody: any; // note: "any" will get overridden in inference
    responses: any;
}

export type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207;

// util
/** Get a union of paths which have method */
export type PathsWith<Paths extends Record<string, PathItemObject>, PathnameMethod extends HttpMethod> = {
    [Pathname in keyof Paths]: Paths[Pathname] extends { [K in PathnameMethod]: any } ? Pathname : never;
}[keyof Paths];
/** 查找keys的第一个匹配项 */
export type FilterKeys<Obj, Matchers> = { [K in keyof Obj]: K extends Matchers ? Obj[K] : never }[keyof Obj];
/** 处理 "application/json", "application/vnd.api+json", "appliacation/json;charset=utf-8" 等等 */
export type JSONLike = `${string}json${string}`;

export type Params<O> = O extends { parameters: any } ? { params: NonNullable<O["parameters"]["query"]> } : BaseParams;

/**
 * @description 通用类型
 * */

/** @description 1.提取接口请求参数的 requestBody */
export type RequestBodyObj<O> = O extends { requestBody?: any } ? O["requestBody"] : never;
// 2.进一步提取接口请求参数的 content
export type RequestBodyContent<O> = undefined extends RequestBodyObj<O> ? FilterKeys<NonNullable<RequestBodyObj<O>>, "content"> | undefined : FilterKeys<RequestBodyObj<O>, "content">;
// 3.最后一步: 提取接口请求参数的 "***/json" 部分的模型
export type RequestBodyJSON<O> = FilterKeys<RequestBodyContent<O>, JSONLike> extends never ? FilterKeys<NonNullable<RequestBodyContent<O>>, JSONLike> | undefined : FilterKeys<RequestBodyContent<O>, JSONLike>;
export type RequestBody<O> = undefined extends RequestBodyJSON<O> ? { data?: RequestBodyJSON<O> } : {
    data: RequestBodyJSON<O>
};

export type RequestOptions<T> = Params<T> & RequestBody<T>;
export type Success<O> = FilterKeys<FilterKeys<O, OkStatus>, "content">;

// fetch types
export type XHROptions<T> = RequestOptions<T> & Omit<AxiosRequestConfig, 'data' | 'params'>;
export type XHRResponse<T> = AxiosResponse<T extends {
    responses: any
} ? NonNullable<FilterKeys<Success<T["responses"]>, JSONLike>> : unknown>

type Paths = Record<string, PathItemObject>
export default function createClient<Paths extends {}>(axios: AxiosStatic) {
    async function coreXHR<P extends keyof Paths, M extends HttpMethod>(url: P, fetchOptions: XHROptions<M extends keyof Paths[P] ? Paths[P][M] : never>): Promise<XHRResponse<M extends keyof Paths[P] ? Paths[P][M] : unknown>> {
        // 发送 axios 请求
        return axios({...fetchOptions, url: url as string})
    }

    return {
        async get<P extends PathsWith<Paths, "get">>(url: P, axiosConfig: XHROptions<FilterKeys<Paths[P], "get">>) {
            return coreXHR<P, "get">(url, {...axiosConfig, method: "GET"} as any);
        },
        async put<P extends PathsWith<Paths, "put">>(url: P, axiosConfig: XHROptions<FilterKeys<Paths[P], "put">>) {
            return coreXHR<P, "put">(url, {...axiosConfig, method: "PUT"} as any);
        },
        async post<P extends PathsWith<Paths, "post">>(url: P, axiosConfig: XHROptions<FilterKeys<Paths[P], "post">>) {
            return coreXHR<P, "post">(url, {...axiosConfig, method: "POST"} as any);
        },
        async del<P extends PathsWith<Paths, "delete">>(url: P, axiosConfig: XHROptions<FilterKeys<Paths[P], "delete">>) {
            return coreXHR<P, "delete">(url, {...axiosConfig, method: "DELETE"} as any);
        },
        async options<P extends PathsWith<Paths, "options">>(url: P, axiosConfig: XHROptions<FilterKeys<Paths[P], "options">>) {
            return coreXHR<P, "options">(url, {...axiosConfig, method: "OPTIONS"} as any);
        },
        async head<P extends PathsWith<Paths, "head">>(url: P, axiosConfig: XHROptions<FilterKeys<Paths[P], "head">>) {
            return coreXHR<P, "head">(url, {...axiosConfig, method: "HEAD"} as any);
        },
        async patch<P extends PathsWith<Paths, "patch">>(url: P, axiosConfig: XHROptions<FilterKeys<Paths[P], "patch">>) {
            return coreXHR<P, "patch">(url, {...axiosConfig, method: "PATCH"} as any);
        },
    };
}
